#!/usr/bin/env python
# -*- coding:utf-8 -*-

import os, sys, signal
import asyncore, asynchat, socket, threading, time
import base64


def DEBUG(format, *args) : print format % args

will_stop = False

def signal_stop( signo, frame ) :
    global  will_stop
    DEBUG( "SERVER catch #%d signal, would stop.", signo )
    will_stop = True
    return
    # signal_stop


#######################################################################

MIN_PACKET_ID = 1
MAX_PACKET_ID = 65535
packet_id_current = MAX_PACKET_ID

class   Packet(object)  :
    def __init__(self) :
        self.action = None
        self.service = None
        self.body = None
        self.packid = 0
        self.respid = 0
        # __init__()

    def isComplete(self) :
        if not self.action or not self.service or not self.body :
            return  False
        return  True

    def isRequest(self) :
        if 'REQUEST' == self.action : return True
        return  False
    # class Packet

def depack(record) :
    '''decode packet record string into Packet struct'''
    field_separator = '\t'
    kv_separator = ':'
    packet = Packet()
    fields = record.split(field_separator)
    for f in fields :
        (key, value) = f.split(kv_separator, 1)
        if not value : continue
        if 'action' == key :
            packet.action = value
        elif 'service' == key :
            packet.service = value
        elif 'packid' == key :
            packet.packid = int(value)
        elif 'respid' == key :
            packet.respid = int(value)
        elif 'body' == key :
            packet.body = base64.b64decode(value)

    if not packet.isComplete() : return None
    return  packet
    # depack()

def enpack(packet) :
    '''encode Packet struct into packet record string'''
    packid = packet.packid
    if not packid :  packid = nextPacketID()
    record = "action:%s\tservice:%s\tpackid:%d" % (packet.action, packet.service, packid)
    if packet.respid > 0 :
        record += "\trespid:%d" % packet.respid
    record += "\tbody:%s" % base64.b64encode(packet.body)
    return  record
    # enpack

def nextPacketID() :
    global  packet_id_current
    if packet_id_current == MAX_PACKET_ID :
        packet_id_current = MIN_PACKET_ID
    else :
        packet_id_current += 1
    return  packet_id_current
    # packetid()

#######################################################################

class   Consumer(object) :
    def response(self, packet, channel) :
        msg = enpack(packet)
        channel.push(msg + '\n')
        # response()

    def feed(self, packet, channel_from, dispatcher) :
        raise NotImplementedError
        # feed()
    # class Consumer()

class   Context(object) :
    '''Context -- context for Consumer'''
    def __init__(self, dispatcher) :
        self.dispatcher = dispatcher
        # __init__()
    # class Context

class   Dispatcher(object)  :
    '''Dispatcher -- dispatch message to consumer'''
    def __init__(self) :
        # table-channel : address --> channel
        self.__table_channel = {}
        # table-consumer : SERVICE --> consumer
        self.__table_consumer = {}
        # __init__()

    def forward(self, channel_from, record) :
        '''forward a record to a channel
        '''
        pass
        # forward()

    def dispatch(self, channel_from, record) :
        '''dispatch a record to a consumer

        List of Actions
         * parse record to get address info (such as SERVICE name)
         * lookup route-table to get the Consumer who is responsible for the record,
            if No Consumer is responsible for it :
                discard the message(record)
                return False
            OR
            if No Consumer is responsible for it :
                use Router as Consumer (route the message again)
         * feed message to consumer
         * return True
        '''
        # parse record, to get SERVICE
        packet = depack(record)
        if not packet : raise RuntimeError, 'invalid record: %s' % record
        if not packet.isRequest() : raise RuntimeError, 'cannot handle %s action' % packet.action

        service = packet.service
        # find Consumer for the SERVICE
        if service not in self.__table_consumer : return  False
        #    for each SERVICE, organize Consumers as a list, we just use the first consumer
        consumer = self.__table_consumer[service][0]

        # for ECHO service, channel_to === channel_from
        consumer.feed(packet, channel_from, self)

        return  True
        # dispatch()

    def registerConsumer(self, service, consumer) :
        if not service : raise RuntimeError, 'Dispatcher: invalid service to register'
        if not consumer : raise RuntimeError, 'Dispatcher: invalid consumer to register'
        if service in self.__table_consumer :
            self.__table_consumer[service].append(consumer)
        else :
            self.__table_consumer[service] = [consumer,]
        # registerConsumer()
    def unregisterConsumer(self, service, consumer) :
        if not service : raise RuntimeError, 'Dispatcher: invalid service to register'
        if not consumer : raise RuntimeError, 'Dispatcher: invalid consumer to register'
        if service in self.__table_consumer :
            self.__table_consumer[service].remove(consumer)
        # unregisterConsumer()
    # class Dispatcher

#######################################################################

class   JuChannel(asynchat.async_chat) :
    def    __init__(self, connect, address, context) :
        asynchat.async_chat.__init__(self, conn=connect)
        self.__connect = connect
        self.address = address
        self.__buf_incoming = []
        self.__terminator = '\n'
        self.set_terminator(self.__terminator)

        self.__context = context
        # __init__()

    def    collect_incoming_data(self, data) :
        DEBUG('SERVER collect: >>>%s<<<', data)
        self.__buf_incoming.append(data)
        # collect_incoming_data()

    def    found_terminator(self) :
        DEBUG('SERVER found: >>>%s<<<', self.__buf_incoming)
        line = ''.join(self.__buf_incoming)
        self.__buf_incoming = []

        self.__context.dispatcher.dispatch(self, line)
        # found_terminator()

    def    handle_close(self) :
        DEBUG('SERVER close connection for client "%s".', self.address)
        self.close()
        # handle_close()

    # class JuChannel

class   JuServer(asyncore.dispatcher):

    def __init__(self, port, context):
        asyncore.dispatcher.__init__(self)
        self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
        self.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.bind(("", port))
        self.listen(5)
        DEBUG('SERVER listening on PORT:%d ...', port)
        self.context = context
        # __init__()

    def handle_accept(self) :
        connect, client = self.accept()
        DEBUG('SERVER accept client %s', client)

        JuChannel(connect, client, self.context)
        # handle_accept()
    # class JuServer

class   Jujube(object) :
    def __init__(self) :
        dispatcher = Dispatcher()
        context = Context(dispatcher)
        self.__context = context
        self.__listeners = {}
        # __init__()

    def listen(self, port) :
        if port in self.__listeners : return
        listener = JuServer(port, self.__context)
        self.__listeners[port] = listener
        # listen()

    def registerConsumer(self, service, consumer) :
        self.__context.dispatcher.registerConsumer(service, consumer)
        # registerConsumer()

    def unregisterConsumer(self, service, consumer) :
        self.__context.dispatcher.unregisterConsumer(service, consumer)
        # unregisterConsumer()

    def run(self) :
        # mask signal
        DEBUG('os.name: %s', os.name)
        signal.signal( signal.SIGTERM, signal_stop )
        if 'posix' == os.name :
            signal.signal( signal.SIGQUIT, signal_stop )

        try :
            asyncore.loop(timeout=10.0)
        except :
            DEBUG( "SERVER interrupt : %s", `sys.exc_info()[1]` )
            pass
        # run()



#######################################################################


class EchoConsumer(Consumer)  :
    '''Echo Consumer'''
    def feed(self, packet, channel_from, dispatcher) :
        record = packet.body
        packet.respid = packet.packid
        packet.packid = 0
        DEBUG('COMSUMER echo: %s', record)
        self.response(packet, channel_from)
        # feed()
    # class EchoConsumer

HOST = "127.0.0.1"
PORT = 54344

def main():
    s = Jujube()
    s.listen(PORT)

    consumer = EchoConsumer()
    s.registerConsumer('ECHO', consumer)
    s.run()
    # main()

if __name__ == '__main__' :
    main()
    sys.exit(0)
